//
//  TTDiskCache.m
//  Tint
//
//  Created by Kevin Wu on 2/23/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTDiskCache.h"



@interface TTDiskCache (Private)

- (void)createCacheFiles;

- (void)saveAfterDelay;
- (void)saveCacheDictionary;

@end

@implementation TTDiskCache (Private)

- (void)createCacheFiles {
    [[NSFileManager defaultManager] createDirectoryAtPath:[self cacheDirectory] 
                              withIntermediateDirectories:YES 
                                               attributes:nil 
                                                    error:NULL];
    
    TTRelease(_cacheDictionary);
    
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[self cachePathForKey:_profileKey]];
    if (TTIsInstance(dict, [NSDictionary class])) {
        _cacheDictionary = [[NSMutableDictionary alloc] initWithDictionary:dict];
        TTRelease(dict);
    } else {
        _cacheDictionary = [[NSMutableDictionary alloc] init];
        TTRelease(dict);
    }
}


- (void)saveAfterDelay {
    SEL selector = @selector(saveCacheDictionary);
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:nil];
    [self performSelector:selector withObject:nil afterDelay:0.3];
}

- (void)saveCacheDictionary {
	@synchronized(self) {
        [_cacheDictionary writeToFile:[self cachePathForKey:_profileKey] atomically:YES];
	}
}

@end




@implementation TTDiskCache


#pragma mark - Memory

- (id)initWithType:(NSString *)type {
    self = [super initWithType:type];
    if (self) {
        _profileKey = [[NSString alloc] initWithString:@"profile.plist"];
        [self createCacheFiles];
        [self cleanUp];
        [self saveCacheDictionary];
    }
    return self;
}

- (void)dealloc {
    TTRelease(_cacheDictionary);
    TTRelease(_profileKey);
    [super dealloc];
}



#pragma mark - Public

- (NSString *)stringForKey:(NSString *)key {
    NSData *data = [self dataForKey:key];
    if (data) return [NSString stringWithData:data];
    return nil;
}

- (void)setString:(NSString *)string forKey:(NSString *)key {
    [self setString:string forKey:key withTimeoutInterval:_defaultTimeoutInterval];
}

- (void)setString:(NSString *)string forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval {
    [self setData:[string dataUsingEncoding:NSUTF8StringEncoding] forKey:key withTimeoutInterval:timeoutInterval];
}


- (UIImage *)imageForKey:(NSString *)key {
    NSData *data = [self dataForKey:key];
    if (key) return [UIImage imageWithData:data];
    return nil;
}

- (void)setImage:(UIImage *)image forKey:(NSString *)key {
	[self setImage:image forKey:key withTimeoutInterval:_defaultTimeoutInterval];
}

- (void)setImage:(UIImage *)image forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval {
	[self setData:UIImagePNGRepresentation(image) forKey:key withTimeoutInterval:timeoutInterval];
}


- (NSData *)dataForKey:(NSString *)key {
    if([self hasCacheForKey:key]) {
        return [NSData dataWithContentsOfFile:[self cachePathForKey:key]];
	}
    return nil;
}

- (void)setData:(NSData *)data forKey:(NSString *)key {
    [self setData:data forKey:key withTimeoutInterval:_defaultTimeoutInterval];
}

- (void)setData:(NSData *)data forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval {
    if (!TTIsInstance(data, [NSData class]) || !TTIsStringWithText(key) || timeoutInterval<0.0) return;
    if ([_profileKey isEqualToString:key]) return;
    
    NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInterval];
    [_cacheDictionary setObject:expirationDate forKey:key];
    [self saveAfterDelay];
    
    [data writeToFile:[self cachePathForKey:key] atomically:YES];
}

- (void)copyFileAtPath:(NSString *)filePath asKey:(NSString *)key {
    [self copyFileAtPath:filePath asKey:key withTimeoutInterval:_defaultTimeoutInterval];
}

- (void)copyFileAtPath:(NSString *)filePath asKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval {
    if (!TTIsStringWithText(key) || timeoutInterval<0) return;
    if ([_profileKey isEqualToString:key]) return;
    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) return;
    
    NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInterval];
    [_cacheDictionary setObject:expirationDate forKey:key];
    [self saveAfterDelay];
    
    NSString *toPath = [self cachePathForKey:key];
    if (![toPath isEqualToString:filePath]) {
        [[NSFileManager defaultManager] copyItemAtPath:filePath toPath:toPath error:NULL];
    }
}

- (void)moveFileAtPath:(NSString *)filePath asKey:(NSString *)key {
    [self moveFileAtPath:filePath asKey:key withTimeoutInterval:_defaultTimeoutInterval];
}

- (void)moveFileAtPath:(NSString *)filePath asKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval {
    if (!TTIsStringWithText(key) || timeoutInterval<0) return;
    if ([_profileKey isEqualToString:key]) return;
    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) return;
    
    NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInterval];
    [_cacheDictionary setObject:expirationDate forKey:key];
    [self saveAfterDelay];
    
    NSString *toPath = [self cachePathForKey:key];
    if (![toPath isEqualToString:filePath]) {
        [[NSFileManager defaultManager] moveItemAtPath:filePath toPath:toPath error:NULL];
    }
}


- (NSString *)cachePathForKey:(NSString *)key {
    return [[self cacheDirectory] stringByAppendingPathComponent:key];
}

- (NSString *)cacheDirectory {
    return [TTCachesPath() stringByAppendingPathComponent:_cacheType];
}

+ (CGFloat)diskCacheSize {
    CGFloat size = 0.0;
    for (TTDiskCache *dc in diskCacheObjects) {
        size += ([dc totalSize]);
    }
    return size;
}

+ (NSArray *)allCacheTypes {
    return [diskCacheObjects valueForKeyPath:@"@distinctUnionOfObjects.cacheType"];
}

- (CGFloat)totalSize {
    
    CGFloat size = 0.0;
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *keys = [_cacheDictionary allKeys];
    
    for (NSString *key in keys) {
        NSString *path = [self cachePathForKey:key];
        NSError *error = nil;
        NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
        if (error == nil) {
            unsigned long long bytes = [attributes fileSize];
            size += (bytes / 1024.0 / 1024.0);
        }
    }
    return size;
}

- (NSDate *)dateOfKey:(NSString *)key {
    if (TTIsStringWithText(key)) {
        NSString *path = [self cachePathForKey:key];
        NSError *error = nil;
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
        if (error == nil) {
            return [attributes fileModificationDate];
        }
    }
    return nil;
}


#pragma mark - Override

+ (id)sharedObject {
    return [self cacheByType:@"DiskCache"];
}


static NSMutableArray *diskCacheObjects = nil;

+ (id)cacheByType:(NSString *)type {
    if (diskCacheObjects == nil) diskCacheObjects = [[NSMutableArray alloc] init];
    
    TTDiskCache *diskCache = [diskCacheObjects firstObjectForKeyPath:@"cacheType" equalToValue:type];
    if (diskCache == nil) {
        diskCache = [[[self alloc] initWithType:type] autorelease];
        [diskCacheObjects addObject:diskCache];
    }
    return diskCache;
}



- (void)clearCache {
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [[NSFileManager defaultManager] removeItemAtPath:[self cacheDirectory] error:NULL];
    [self createCacheFiles];
    [self saveCacheDictionary];
}

- (void)cleanUp {
    NSEnumerator *keyEnumerator = [_cacheDictionary keyEnumerator];
    NSString *key = nil;
    
    NSMutableArray *keysToBeRemoved = [NSMutableArray array];
    while ((key = [keyEnumerator nextObject])) {
        NSDate *date = [_cacheDictionary objectForKey:key];
        if ([[NSDate date] isLaterThanDate:date]) {
            [keysToBeRemoved addObject:key];
            [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:NULL];
        }
    }
    
    if (TTIsArrayWithItems(keysToBeRemoved)) {
        [_cacheDictionary removeObjectsForKeys:keysToBeRemoved];
    }
}

- (void)removeCacheForKey:(NSString *)key {
    if ([_profileKey isEqualToString:key]) return;
    
    [_cacheDictionary removeObjectForKey:key];
    [self saveCacheDictionary];
    
    [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:NULL];
}


- (BOOL)hasCacheForKey:(NSString *)key {
    if (!TTIsStringWithText(key))
        return NO;
    
    NSDate *date = [_cacheDictionary objectForKey:key];
    
	if(!TTIsInstance(date, [NSDate class]))
        return NO;
    
    if ([[NSDate date] isLaterThanDate:date])
        return NO;
    
	return [[NSFileManager defaultManager] fileExistsAtPath:[self cachePathForKey:key]];
}

- (NSArray *)cacheKeys {
    return [_cacheDictionary allKeys];
}

- (NSUInteger)count {
    return [_cacheDictionary count];
}

@end

